目前表格顯示的是假資料,我們要從SpringBoot專案取得真實的資料。
我們選擇的SpringBoot專案是待辦事項清單(第二個專案)。
安裝axios,用來和RESTful API溝通。
bun i axios
在Home.jsx添加對應的內容,來取得Todo。
import React, { useEffect, useState } from "react";
import axios from "axios";
export default function Home() {
使用useEffect,在讀取網頁時執行裡面的程式
useEffect(() => {
loadTodos();
}, []);
從後端讀取Todo資料,是非同步的操作,所以要用async/await
const loadTodos = async () => {
發送GET請求,讀取所有的Todo,await會等待這個GET請求完成,才繼續往下執行
const res = await axios.get("http://localhost:8080/api/todo/all");
在F12的Console顯示取得的內容
console.log(res.data);
};
//skip
}
現在我們需要修改後端的程式,解決CORS的問題,讓前端能夠使用後端的API。
我們回到Spring Boot專案,修改TodoController.java
@CrossOrigin("http://localhost:5173/")
public class TodoController {
啟動後端專案,同時也啟動前端專案,按下F12選擇Console頁面,重新整理網頁,可以看到有一行
[]
代表我們解決了CORS,前端可以連接後端的API。
目前資料庫中沒有Todo,現在要讓前端能夠新增Todo。
建立src/components/todo/AddTodo.jsx,在網頁上完成新建Todo。
import React, { useState } from "react";
import axios from "axios";
export default function AddTodo() {
const [todo, setTodo] = useState({
title: "",
completed: false
})
const {title, completed} = todo;
const onInputChange = (e) => {
const { name, value, type, checked } = e.target;
setTodo({
...todo,
[name]: type === "checkbox" ? checked : value
});
};
const onSubmit = async (e) => {
e.preventDefault();
await axios.post("http://localhost:8080/api/todo/", todo);
}
return (
<div className="flex justify-center min-h-screen items-center bg-gray-100">
<form
method="post"
role="form"
className="bg-white p-6 rounded-lg shadow-md w-full max-w-md"
onSubmit={e => onSubmit(e)}
>
<div className="mb-4">
<label className="text-gray-700 font-bold mb-2">
Title
</label>
<input
placeholder="Enter todo title"
type="text"
className="shadow border rounded w-full py-2 px-3 text-gray-700"
name="title"
value={title}
onChange={e => onInputChange(e)}
/>
</div>
<div className="mb-4">
<label className="text-gray-700 font-bold mb-2">
Completed
</label>
<br />
<input type="checkbox" className="mr-2 leading-tight" name="completed" checked={completed} onChange={e => onInputChange(e)} />
</div>
<button
type="submit"
className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"
>
Add
</button>
</form>
</div>
);
}
接下來啟用導航列,讓我們能夠在首頁和新增Todo的頁面之間切換。
安裝react-router-dom,實現路由功能。
bun i react-router-dom
修改App.jsx,添加路由內容。
import React from "react";
import Navbar from "./components/navbar/Navbar";
import Home from "./pages/Home";
import AddTodo from "./components/todo/AddTodo";
import {BrowserRouter as Router, Route, Routes} from "react-router-dom";
function App() {
return (
<>
<Router>
<Navbar />
<Routes>
<Route exact path="/" element={<Home />}></Route>
<Route exact path="/add" element={<AddTodo />}></Route>
</Routes>
</Router>
</>
);
}
export default App;
http://localhost:5173/會進入首頁。
http://localhost:5173/add 是新增Todo的網頁。
對Navbar.jsx,做點小修改,讓我們使用它來進入不同的頁面。
//previous content
const navigation = [
{ name: "Home", href: "/", current: false },
{ name: "Add Todo", href: "/add", current: false },
];
//previous content
修改AddTodo,當按下送出後,會回到首頁。
let navigate = useNavigate();
const onSubmit = async (e) => {
e.preventDefault();
await axios.post("http://localhost:8080/api/todo/", todo);
navigate("/");
}
記得import useNavigate,不然會出錯,只會顯示空白畫面。
現在我們將真實的資料顯示在首頁上,修改Home.jsx。
const [todos, setTodos] = useState([]);
將從後端取得的資料傳入todos
const loadTodos = async () => {
//skip
setTodos(res.data);
};
todos.map((todo)和thymeleaf中的th:each類似,將todos的資料一個一個呈現
<tbody>
{todos.map((todo) => (
<tr className="bg-white hover:bg-gray-100">
<td className="border border-gray-300 px-4 py-2">{todo.id}</td>
<td className="border border-gray-300 px-4 py-2">{todo.title}</td>
<td className="border border-gray-300 px-4 py-2">
{todo.completed ? "Yes" : "No"}
</td>
<td className="border border-gray-300 px-4 py-2">
<div className="space-x-2">
<a href="#" className="text-blue-500 hover:underline">
View
</a>
<a href="#" className="text-orange-500 hover:underline">
Edit
</a>
<a href="#" className="text-green-500 hover:underline">
Set Completed
</a>
<a href="#" className="text-yellow-500 hover:underline">
Set Uncompleted
</a>
<a href="#" className="text-red-500 hover:underline">
Delete
</a>
</div>
</td>
</tr>
))}
</tbody>
來到新增頁面,填寫Title、選擇Completed後,按下Add。
就可以在首頁見到剛新增的todo
我們先完成這幾個不需要新增網頁就能運作的功能,修改Home.jsx。
使用useParams取得參數
const {id} = useParams();
將todo狀態改為已完成
const setCompletedTodo = async (id) => {
await axios.patch(`http://localhost:8080/api/todo/${id}/completed`);
loadTodos();
};
將todo狀態改為未完成
const setUnCompletedTodo = async (id) => {
await axios.patch(`http://localhost:8080/api/todo/${id}/uncompleted`);
loadTodos();
};
刪除Todo
const deleteTodo = async (id) => {
await axios.delete(`http://localhost:8080/api/todo/${id}`);
loadTodos();
};
修改表格的td部分,在按下時做出對應的動作。
<a onClick={() => setCompletedTodo(todo.id)} className="text-green-500 hover:underline">
Set Completed
</a>
<a onClick={() => setUnCompletedTodo(todo.id)} className="text-yellow-500 hover:underline">
Set Uncompleted
</a>
<a onClick={() => deleteTodo(todo.id)} className="text-red-500 hover:underline">
Delete
</a>
在可以在網頁點擊,做出以下的功能
原先Completed是No,按下後變為Yes。
原先Yes,按下變No。
我們複製貼上AddTodo.jsx的內容到EditTodo.jsx。
將AddTodo改成EditTodo
export default function EditTodo()
我們需要使用useParams取得id,增加這一行
const {id} = useParams();
傳送資料的網址不同了,因此要修改
await axios.put(`http://localhost:8080/api/todo/${id}`, todo);
按鈕的文字從Add改成Edit
<button
type="submit"
className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"
>
Edit
</button>
我們需要取得特定的todo資料,添加以下程式碼
useEffect(() => {
loadTodo();
}, []);
const loadTodo = async () => {
const res = await axios.get(`http://localhost:8080/api/todo/${id}`);
setTodo(res.data);
}
在App.jsx,增加這行,處理修改Todo資料的路徑,以及id會當作參數
<Route exact path="/edit/:id" element={<EditTodo />}></Route>
修改Home.jsx的Edit區塊href的目的地
<a href={`/edit/${todo.id}`} className="text-orange-500 hover:underline">
Edit
</a>
現在我們可以透過Edit來修改Todo內容。
按下Edit,前往編輯頁面,修改todo後,按下Edit送出。
回到首頁,發現todo內容變了。
我們使用EditTodo的結構,來完成ViewTodo.jsx。
function名稱改成ViewTodo
export default function ViewTodo()
刪除onInputChange
修改onSubmit
const onSubmit = (e) => {
e.preventDefault();
navigate("/");
};
把form改成div,只留下className的部分
<div className="bg-white p-6 rounded-lg shadow-md w-full max-w-md">
將input改成div
<div className="mr-2 leading-tight">{todo.title}</div>
<div className="mr-2 leading-tight">
{todo.completed ? "Yes" : "No"}
</div>
按鈕的文字改成Back,處理按下時的事件
<button
type="submit"
className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"
onClick={(e) => onSubmit(e)}
>
Back
</button>
在App.jsx,增加
<Route exact path="/view/:id" element={<ViewTodo />}></Route>
修改Home.jsx
<a href={`/view/${todo.id}`} className="text-blue-500 hover:underline">
View
</a>
我們就能透過View來查看單一Todo的內容了,按Back就能回到首頁。
我們的前端React專案完成了,比之前的全端專案更好看。
需要自行安裝使用到的npm套件,在專案目錄輸入npm i或bun i即可安裝。
https://mega.nz/file/JctCSbpa#Yq77_mo00a9CNl9AC-cjWpiRy68-rbOg0AvJaIlQYX4